﻿//-----------------------------------------------------------------------
// <copyright file="ColorRamp.cs" company="Flaxen Geo">
//    Copyright (c) 2011, Michael P. Gerlek.  All rights reserved.
// </copyright>
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, 
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Flaxen Geo nor the names of its contributors may be 
//   used to endorse or promote products derived from this software without 
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
// THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------

namespace Flaxen.SlimDXControlLib
{
    using System;
    using SlimDX;

    /// <summary>
    /// This class provides a standard Blue-(Cyan)-Green-(Yellow)-Red ramp function.
    /// That is, given a range [min..max], if you give it a value it will return a color
    /// "between" red and blue corresponding to where the value is in the range.
    /// The code was adapted from the nice example here:
    ///   http://ozviz.wasp.uwa.edu.au/~pbourke/texture_colour/colourramp/
    /// (More ramps can be found there, incl. more C# code.)
    /// </summary>
    public class ColorRamp
    {
        private readonly float m_minValue;
        private readonly float m_maxValue;
        private readonly float m_dv;
        private readonly float m_dv14;
        private readonly float m_r1;
        private readonly float m_r2;
        private readonly float m_r2m;
        private readonly float m_r3;

        /// <summary>
        /// Initializes a new instance of the ColorRamp class.
        /// </summary>
        /// <param name="minValue">minimum value of range</param>
        /// <param name="maxValue">maximum value of range</param>
        public ColorRamp(float minValue, float maxValue)
        {
            if (maxValue <= minValue)
            {
                throw new ArgumentOutOfRangeException("maxValue", "maxValue not greater than minValue");
            }

            m_minValue = minValue;
            m_maxValue = maxValue;

            m_dv = m_maxValue - m_minValue;
            m_dv14 = m_dv / 4.0f;

            m_r1 = m_minValue + (m_dv * 0.25f);
            m_r2 = m_minValue + (m_dv * 0.50f);
            m_r2m = m_minValue - (m_dv * 0.50f);
            m_r3 = m_minValue + (m_dv * 0.75f);
        }

        /// <summary>
        /// Method to verify that the modified instance method matches the original static method.
        /// </summary>
        public static void Test()
        {
            Random r = new Random();

            for (int i = 0; i < 10000; i++)
            {
                float vmin = (float)r.NextDouble();
                float vmax = (float)r.NextDouble();
                if (vmin >= vmax)
                {
                    float t = vmin;
                    vmin = vmax;
                    vmax = t;
                }

                float v = (float)r.NextDouble();
                ColorRamp ramp = new ColorRamp(vmin, vmax);
                Color3 c1 = ramp.GetColor(v);
                Color3 c2 = ColorRamp.GetColor(v, vmin, vmax);
                System.Diagnostics.Debug.Assert(c1.Red == c2.Red, "reds differ");
                System.Diagnostics.Debug.Assert(c1.Green == c2.Green, "greens differ");
                System.Diagnostics.Debug.Assert(c1.Blue == c2.Blue, "blues differ");
            }

            return;
        }

        /// <summary>
        /// Returns the color of the value from within the given range.
        /// Note values that are out of range will be clamped.
        /// </summary>
        /// <param name="value">value to determine color of</param>
        /// <param name="minValue">minimum value of range</param>
        /// <param name="maxValue">maximum value of range</param>
        /// <returns>the color of the value</returns>
        public static Color3 GetColor(float value, float minValue, float maxValue)
        {
            Color3 c = new Color3(1, 1, 1); // white

            if (value < minValue)
            {
                value = minValue;
            }

            if (value > maxValue)
            {
                value = maxValue;
            }

            float dv = maxValue - minValue;

            if (value < (minValue + (0.25 * dv)))
            {
                c.Red = 0;
                c.Green = 4 * (value - minValue) / dv;
            }
            else if (value < (minValue + (0.5 * dv)))
            {
                c.Red = 0;
                c.Blue = 1 + (4 * (minValue + (0.25f * dv) - value) / dv);
            }
            else if (value < (minValue + (0.75 * dv)))
            {
                c.Red = 4 * (value - minValue - (0.5f * dv)) / dv;
                c.Blue = 0;
            }
            else
            {
                c.Green = 1 + (4 * (minValue + (0.75f * dv) - value) / dv);
                c.Blue = 0;
            }

            return c;
        }

        /// <summary>
        /// Returns the color of the value from within the given range.
        /// Note values that are out of range will be clamped.
        /// </summary>
        /// <param name="value">value to determine color of</param>
        /// <returns>the color of the value</returns>
        public Color3 GetColor(float value)
        {
            Color3 c = new Color3();

            if (value < m_minValue)
            {
                value = m_minValue;
            }

            if (value > m_maxValue)
            {
                value = m_maxValue;
            }

            if (value < m_r1)
            {
                c.Red = 0;
                c.Green = m_dv14 * (value - m_minValue);
                c.Blue = 1;
            }
            else if (value < m_r2)
            {
                c.Red = 0;
                c.Green = 1;
                c.Blue = 1 + (m_dv14 * (m_r1 - value));
            }
            else if (value < m_r3)
            {
                c.Red = m_dv14 * (value - m_r2m);
                c.Green = 1;
                c.Blue = 0;
            }
            else
            {
                c.Red = 1;
                c.Green = 1 + (m_dv14 * (m_r3 - value));
                c.Blue = 0;
            }

            return c;
        }
    }
}
